iT邦幫忙

2022 iThome 鐵人賽

DAY 9
1
Modern Web

Rails,我要進來囉系列 第 9

第九天:ActionCable = Rails + websocket

  • 分享至 

  • xImage
  •  

開場白

鼬~~哩賀,我是寫程式的山姆老弟,昨天跟大家一起看了一點 ActionMailer 串接 AWS SES 來寄信 的使用方式,今天就來看一下 ActionCable ,夠夠~

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day9-1.png

ActionCable 負責 WebSocket

什麼是 websocket 呢?就是可以讓 Client 端和 Server 端,建立起一道專屬的雙向溝通通道,Client 端和 Server 端可以透過這個通道互相傳訊息,雖然說是雙向的通道,但主要是設計給 Server 傳訊息給 Client 端的,為什麼呢?因為 Client 要傳訊息給 Server 有太多方法了(Web APIs),但 Server 要傳給 Client 端卻很難,主要是 Server 無法得知 Client 是不是準備好接收資料。

這個雙向通道的話呢,是使用 Publish/Subscribe 的設計模式,沒錯,就是我們在 Youtube 會使用的 訂閱 功能,當你訂閱某個頻道之後,如果頻道有更新,你就會收到頻道更新的推播通知,就是這樣子的概念。

ActionCable 就是方便我們在開發網頁時,可以讓 Server 推播訊息給網頁用戶們,在還沒有 ActionCable 出現之前,Rails 的開發者還需要自己使用第三方 gem 來整合 websocket,然後另外開 websocket server 來做這些事,現在有 ActionCable 之後,可以不需要另外開 websocket server 了,直接整合在 Rails server 裡,而且可以和 ActiveRecord 整合,直接使用資料庫內的資料推播,是真的挺方便的,不過在設定的過程中,還是遇到一些坑,待會跟大家分享~

使用 ActionCable 的組成

因為 websocket 是要建立 Client 端和 Server 端的通訊,所以 ActionCable 分為兩個部分來設定,一個是 Server 端的設定,主要是要「替 clients 提供通道」、「透過通道廣播訊息給 Subscribers」,另一個是 Client 端的設定(如果你的 Rails server 只是 API server、前端是獨立拆出來做的話,就可以跳過 Client 端的設定),主要是要「連上 websocket 後要做什麼反應?」、「接受訊息後該在網頁上做什麼反應?」、「斷線時要做什麼反應?」

Server 端的設置

假設我們要做的是,建立 Server 向網頁使用者的通知管道

  1. 用 rails generator 產生一個新的 channel

    $ rails g channel notification

     	 invoke  test_unit
       create    test/channels/notification_channel_test.rb
    identical  app/channels/application_cable/channel.rb
    identical  app/channels/application_cable/connection.rb
       create  app/channels/notification_channel.rb
       create  app/javascript/channels/notification_channel.js
         gsub  app/javascript/channels/notification_channel.js
       append  app/javascript/channels/index.js
    

    產生幾個檔案,其中就已經包含了 server 端設置需要的檔案 + client 端需要的檔案

    我們先到 app/channels/notification_channel.rb 看,然後用 stream_from 來建立一個叫做 notification_channel 的通道,並且新增一個叫做 def receive(data) 的 method,這個 method 是讓 client 透過 websocket 傳訊息過來的地方

    # app/channels/notification_channel.rb
    class NotificationChannel
    	def subscribed
    		puts("Someone subscribed")
    		stream_from("notification_channel")
    	end
    
    	def unsubscribed
    		puts("Someone unsubscribed")
    	end
    
    	def receive(data)
    		puts("Received: #{data}")
    	end
    end
    
  2. 綁定 websocket 位址(有兩種綁定方法,擇一就好)

    方法一:透過 config/routes.rb

    這邊比較常見的有綁定 '/cable' 也有 '/websocket',目前我看起來是沒什麼差別

    Rails.application.routes.draw do 
    	mount ActionCable.server => '/websocket'
    end
    

    方法二:透過 config/application.rb

    這邊比較常見的有綁定 '/cable' 也有 '/websocket',目前我看起來是沒什麼差別

    config.action_cable.mount_path = '/websocket'
    
  3. 隨便放個空白首頁,$ rails g controller home_controller index,再把 routes 的 root 指過去

    # config/routes.rb
    root 'home#index'
    

到這邊就已經設定完 server 端的部分了~

Client 端的設置

這邊要注意一點,Rails 是個全端框架,代表雖然你看起來都是在 Rails 裡面開發,但有些程式碼不是在 Rails server 執行,而是透過網路把程式碼送到 Client 端的瀏覽器裡面執行,也就是 html、js、css 等檔案,要區分一下這個概念,在開發的時候才不會搞不清楚現在在做什麼

  1. app/views/layouts/application.html.erb 的 ,加入 action_cable_meta_tag

    這點就是我踩很久的雷坑QQ,我爬了很多文章,也幾乎都沒有寫這行,官方文件也是很簡略地用文字帶過,沒認真看真的是會漏掉

    <!-- app/views/layouts/application.html.erb -->
    <!DOCTYPE html>
    <html>
      <head>
        ...
        <%= action_cable_meta_tag %>
      </head>
    
      <body>
        <%= yield %>
      </body>
    </html>
    

    這個 helper tag,會在 html 產生,也就是幫我們告訴瀏覽器說,websocket 的位址在哪裡,如果你在 server 設定的位址是 /cable,那這個 action_cable_meta_tag 也會跟著改成 /cable

    <html>
    	<head>
    		<meta name="action-cable-url" content="/websocket">
    		...
    	</head>
    	...
    </html>
    
  2. app/javascript/channels/consumer.js 從 actioncable 引入 createConsumer function,方便其他 channel 使用

    import { createConsumer } from '@rails/actioncable'
    export default createConsumer()
    
  3. /app/javascript/channels/notification_channel.js 訂閱 Notification Channel,這樣 server 才能透過這個 channel 傳訊息到網頁使用者,這邊設定三個 function,分別是 connecteddisconnectedreceived(data),分別代表「訂閱成功建立連線時要做的反應」、「取消訂閱斷開連線時的反應」、「接收到來自 server 的訊息該做的反應」,我們先設定接收到訊息的時候,就先跳出個警告匡就好

    ps. 注意這邊的 js code 是在使用者的瀏覽器執行的

    // /app/javascript/channels/notification_channel.js
    import consumer from "channels/consumer"
    
    consumer.subscriptions.create("NotificationChannel", {
      connected() {
        console.log("Connected to NotificationChannel")
      },
    
      disconnected() {
    		console.log("Disconnected to NotificationChannel")
      },
    
      received(data) {
        // Called when there's incoming data on the websocket for this channel
        alert("Received: " + data['message'])
      }
    });
    

到這邊 client 端也設置好了~

驗證 Websocket 通道

  1. $ rails s 啟動 rails server

  2. 打開瀏覽器,到 127.0.0.1:3000/

  3. 檢查 Terminal 有沒有相關的 log,如果沒有,那應該是上面有做錯,請檢查一下

    Started GET "/websocket" for 127.0.0.1 at 2022-09-14 10:00:04 +0800
    Started GET "/websocket" [WebSocket] for 127.0.0.1 at 2022-09-14 10:00:04 +0800
    Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
    NotificationChannel is transmitting the subscription confirmation
    NotificationChannel is streaming from notification_channel
    
  4. 讓 Server 來推送一些訊息試試看,$ rails c

    [1] pry(main)> ActionCable.server.broadcast('notification_channel', '你好啊')
    
  5. 網頁上應該就會收到訊息,到這邊就完成初步的 ActionCable 連接拉~

    https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day9-2.png

總結

action_cable_meta_tag 真的讓我卡很久,超級無敵煩躁,想說奇怪我都照著做了,文章我也都算認真看了,怎麼還是沒出來 (╯•̀ὤ•́)╯

最後我好像是到 youtube 查有沒有人實作的影片,看到某一部有做這個設定,我才照著加上去,結果就可以了,事後我去翻 RailsGuide 文件,結果佔超小篇幅,直接被我忽略 XD

https://raw.githubusercontent.com/shrimp509/my-img-host/master/relacs-studio/Rails%E6%88%91%E8%A6%81%E9%80%B2%E4%BE%86%E5%9B%89/day9-3.png

總之好不容易有試成功,明天來試試看用 ActionCable 做點應用吧,我們明天見~


上一篇
第八天:ActionMailer 串 AWS SES 寄信
下一篇
第十天:用 ActionCable 做出簡易聊天室
系列文
Rails,我要進來囉30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言